一些特别棒的面试题[2]

由于种种原因,开始了新一轮的面试,这一轮面试可谓收获颇丰。
与各种各样的面试官交流下来,除了收获到一些疏漏的知识点外,发现面试其实非常考验面试官的水平。揣摩出一些如何成为一名合格的前端面试官方法。

以及很重要的老哥的经验:进大厂前必须要做的准备,提前一个月刷题。

如何成为一名合格的面试官?

  • 学会倾听
    无论面试者有多少职场上的不如意想和你倾诉,都要耐心听他讲完,不要打断他
  • 学会定位
    根据面试者的工作经验和工作场景,对能力进行简单定位,虽然这偶尔会有所偏颇,但是对于后续提问会有很好的帮助
  • 学会理解
    理解面试者想要表达的对知识点的理解,即使他讲的很模糊,尽可能捕捉其理解的部分
  • 学会引导
    当面试者遇到一道棘手的题目时,如果对方逻辑不清楚,学会化繁为简,由浅入深去做引导;假设对方对当前的题目游刃有余,引导深入原理或者是扩展出应用场景
  • 学会挖掘
    学会挖掘出面试者身上的闪光点,聪明,执着,这些都是非常好的品质
  • 学会尊重
    当面试者急于想得知面试官的评价时,无论你对他是否满意,都采取”如果有新的进展,我们后续会再通知你”的方式
  • 学会沉默
    当面试者想得知你对问题的看法时,不要做太深层次的讲解,点到为止,因为有限的面试时间主要目的是为了考察面试者的能力

setTimeout与函数调用栈
console.log(1);
setTimeout(function(){
console.log(2);
},0);
console.log(3);
输出:1 3 2
原因:Call Stack会最后调用setTimeout的callback,setTimeout中的callback是一个异步函数。
函数调用栈的部分可以参考这里:https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/

如何解决callback hell问题

https://blog.risingstack.com/node-js-async-best-practices-avoiding-callback-hell-node-js-at-scale/

function foo 与 var foo的提升优先级

1
2
3
console.log(typeof foo);
var foo = "foo";
function foo(){}

输出:function

1
2
3
console.log(typeof foo);
function foo(){}
var foo = "foo";

输出:function

function优先级比var高,无论在其前后,都会覆盖掉同名的var声明。

let 块作用域 与 setTimeout

1
2
3
4
5
6
for(let i=0;i<6;i++){
setTimeout(function(){
console.log(i)
},0)
}
console.log(i)

输出:

1
2
3
4
5
6
7
0
Uncaught ReferenceError: i is not defined
1
2
3
4
5

  • Uncaught ReferenceError: i is not defined
    由此可见for语句的块作用域,不仅仅在花括号中生效,在圆括号中也生效。
  • 输出0 1 2 3 4 5 的原因
    setTimeout的callback是异步函数,for循环实质上是在做异步循环队列,setTimeout的callback会被调用5次,由于let会为每次的i分配独立的地址空间,因此每一次传不同的值进去。

为什么在debug的过程中,打印顺序是混乱的? (等把规范的timers章节翻译完,再来解决)
breakpoint打在console.log(i)上。

1
2
3
4
5
6
7
Uncaught ReferenceError: i is not defined
0
2
5
4
3
1

如果将let替换成var呢?

1
2
3
4
5
6
for(var i=0;i<6;i++){
setTimeout(function(){
console.log(i)
},0)
}
console.log(i)

输出:
6个6
原因:

  • 打印的是window.i
    每个传入的i指向相同的i,传入时依次window.i的值为1,2,3,4,5,6,但是都是同一个引用,当函数调用栈开始执行setTimeout的callback时,window.i已经变为了6
  • var 不会限制块作用域
    不会分配6个独立的地址空间给setTimeout的callback

深入理解Object.prototype.toString.call()

为什么Object.toString.call([1,2,3])返回[object Array]?可以直接[].toString()返回[object Array]吗?
难道真的像自己理解的那样,是通过call将[1,2,3]作为Object.toString的实参传递了进去吗?不是。
直接Object.toString([1,2,3])不能实现同样的功能吗?不能。
而实际上也有Array.proto.toString()这种形式,所以是可以直接调用arr.toString()的,这样能检测出吗?不行。

那到底是什么原因?
先来肝一个表格。


数据类型 例子 return

字符串|”foo”.toString() |”foo”
数字|1.toString()|Uncaught SyntaxError: Invalid or unexpected token
布尔值|false.toString()|”false”
undefined|undefined.toString()|Uncaught TypeError: Cannot read property ‘toString’ of undefined
null|null.toString()|Uncaught TypeError: Cannot read property ‘toString’ of null
String|String.toString()|”function String() { [native code] }”
Number|Number.toString()|”function Number() { [native code] }”
Boolean|Boolean.toString()|”function Boolean() { [native code] }”
Array|Array.toString()|”function Array() { [native code] }”
Function|Function.toString()|”function Function() { [native code] }”
Date|Date.toString()|”function Date() { [native code] }”
RegExp|RegExp.toString()|”function RegExp() { [native code] }”
Error|Error.toString()|”function Error() { [native code] }”
Promise|Promise.toString()|”function Promise() { [native code] }”
Obejct|Object.toString()|”function Object() { [native code] }”
Math|Math.toString()|”[object Math]”

为什么会出现下面的情况?

1
2
Object.toString.call(Array)//"function Array() { [native code] }"
Object.prototype.toString.call(Array)//"[object Function]"

答案在这里!

1
2
Object.toString()//"function Object() { [native code] }"
Object.prototype.toString()//"[object Object]"

Object对象和它的原型链上各自有一个toString()方法,第一个返回的是一个函数,第二个返回的是值类型。

既然知道了不同,现在我们再来分析下Object.prototype.toString.call(Array)//"[object Function]"
Array对象本身返回一个构造函数,Array//ƒ Array() { [native code] },而Object.prototype.toString()返回的是//“[object Type]”的形式,通过call将Array的this上下文切换到Object,从而调用了Object.prototype.toString(),因此返回[object Function]

需要注意的是:Math.toString()直接返回”[object Math]”。

实际开发中,我们用到最多的可能是:Object.prototype.toString.call([1,2,3])//“[object Array]”这种。

总结:

  • 一般情况下,js中对象的toString(),返回字符串,内容与函数声明语法有关,例如[1,2,3].toString()//“1,2,3”
  • 大多数都返回函数的完整源码,Array.toString()//“function Array() { [native code] }”
  • 内置函数往往返回一个类似”[native code]”的函数体,需要配合call方法,比如Object.prototype.toString.call([1,2,3])//“[object Array]”

那么不可以直接Array.prototype.toString.call([1,3,4])吗?
不行!
因为Array,Function,Date虽然是基于Object进行创建的,但是他们继承的是Object.toString(),而不是Object.prototype.toString()。
再加深一遍印象:

1
2
Object.toString()//"function Object() { [native code] }"
Object.prototype.toString()//"[object Object]"

所以这就是必须用Object.prototype.toString()去检测类型的原因。

至于Object.prototype.toString()内部是怎么实现的,等到时机成熟再去深入。